home *** CD-ROM | disk | FTP | other *** search
/ Amiga Format CD 31 / Amiga Format CD31 (1998-09-02)(Future Publishing)(GB)(Track 1 of 2)[!][issue 1998-10].iso / -seriously_amiga- / hardware / transadf / source / pkzip.c < prev    next >
C/C++ Source or Header  |  1998-07-20  |  19KB  |  559 lines

  1. /* pkzip.c - Handle all PKZip-file specific tasks
  2. ** Copyright (C) 1997,1998 Karl J. Ots
  3. ** 
  4. ** This program is free software; you can redistribute it and/or modify
  5. ** it under the terms of the GNU General Public License as published by
  6. ** the Free Software Foundation; either version 2 of the License, or
  7. ** (at your option) any later version.
  8. ** 
  9. ** This program is distributed in the hope that it will be useful,
  10. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. ** GNU General Public License for more details.
  13. ** 
  14. ** You should have received a copy of the GNU General Public License
  15. ** along with this program; if not, write to the Free Software
  16. ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  17. */
  18.  
  19. /*---------------------*/
  20. /* PKZip file routines */
  21. /*---------------------*/
  22.  
  23. #include <exec/types.h>
  24. #include <dos/dos.h>
  25. #include <clib/dos_protos.h>
  26.  
  27. #include <stdlib.h>
  28. #include <string.h>
  29. #include <ctype.h>
  30.  
  31. #include "pkzip.h"
  32. #include "main.h"
  33. #include "util.h"
  34. #include "errors.h"
  35.  
  36. /* Private functions */
  37. LONG GotoFirstField (BPTR file, ULONG FieldID);
  38.  
  39.  
  40. /*----------------------------------*/
  41. /* Constants, structures and macros */
  42. /*----------------------------------*/
  43.  
  44. extern const char ZipComment[];  /* Defined in version.c */
  45.  
  46. /* PKZip Local Header structure */
  47. struct PKZHead {
  48.   ULONG  MagicNum;          /* Magic Number, = 0x504B0304 ('P','K',3,4).  */
  49.   UBYTE  MinVer;            /* Min. required UnZip version (20 for defl). */
  50.   UBYTE  MinOS;             /* Min. requires OS, MS-DOS = 0.              */
  51.   UWORD  Flags;             /* Flags.                                     */
  52.   UWORD  CMethod;           /* Compression method, deflate = 8.           */
  53.   ULONG  Date;              /* Last moddification date.                   */
  54.   ULONG  CRC;               /* Uncompressed Data CRC.                     */
  55.   ULONG  CSize;             /* Compressed data length.                    */
  56.   ULONG  USize;             /* Uncompressed data length.                  */ 
  57.   UWORD  FNameLen;          /* File name length.                          */
  58.   UWORD  EFieldLen;         /* Extra field length.                        */ 
  59. }; /* sizeof = 30 */
  60.  
  61.  
  62. /* PKZip Central Record structure */
  63. struct PKZCenRec {
  64.   ULONG  MagicNum;          /* Magic Number, = 0x504B0102 ('P','K',1,2).  */
  65.   UBYTE  ZipVer;            /* Zip version number x10 (ie 20 = v2.0).     */
  66.   UBYTE  HostOS;            /* Host OS, Amiga = 1.                        */
  67.   UBYTE  MinVer;            /* Min. required UnZip version (20 for defl). */
  68.   UBYTE  MinOS;             /* Min. requires OS, MS-DOS = 0.              */
  69.   UWORD  Flags;             /* Flags.                                     */
  70.   UWORD  CMethod;           /* Compression method, deflate = 8.           */
  71.   ULONG  Date;              /* Last moddification date.                   */
  72.   ULONG  CRC;               /* Uncompressed Data CRC.                     */
  73.   ULONG  CSize;             /* Compressed data length.                    */
  74.   ULONG  USize;             /* Uncompressed data length.                  */
  75.   UWORD  FNameLen;          /* File name length.                          */
  76.   UWORD  EFieldLen;         /* Extra field length.                        */
  77.   UWORD  FCommentLen;       /* File comment length.                       */
  78.   UWORD  DiskNum;           /* Disk number in multi-part zip.             */
  79.   UWORD  FileType;          /* File Type, set bit 0 for text.             */
  80.   UWORD  MSDAttrib;         /* MS-DOS File Attributes.                    */
  81.   UWORD  Attrib;            /* File Attributes, Amiga rw-d = 0x0D04 (LE). */
  82.   ULONG  LHeadOff;          /* Offset of local header.                    */
  83. }; /* sizeof = 46 */
  84.  
  85.  
  86. struct PKZEndCRec {
  87.   ULONG  MagicNum;          /* Magic Number, = 0x504B0506 ('P','K',5,6).  */ 
  88.   UWORD  DiskNum;           /* Number of this disk in multi-disk zip.     */
  89.   UWORD  CentRecDisk;       /* Disk number with start of Central Record.  */
  90.   UWORD  EntriesOnDisk;     /* Number of entries on this disk.            */
  91.   UWORD  CenRecEntries;     /* Total number of Central Record Entries.    */
  92.   ULONG  CenRecSize;        /* Total Size of Central Record.              */
  93.   ULONG  CenRecOffset;      /* Offset of start of Central Record.         */
  94.   UWORD  CommentLen;        /* Length of Zip Comment.                     */
  95. }; /* sizeof = 22 */
  96.  
  97.  
  98. #define LHD_MAGNUM   0x504B0304  /* Local Header Magic Number          */
  99. #define CRC_MAGNUM   0x504B0102  /* Central Record Magic Number        */
  100. #define ECR_MAGNUM   0x504B0506  /* End of Central Record Magic Number */
  101.  
  102.  
  103. /* Used to store the date/time that compression started */
  104. ULONG  startDate;
  105.  
  106. /* Used to store the location of the `current' PKZip header */
  107. LONG   pkzHeadOffset;
  108. STRPTR pkzOrigName;
  109.  
  110. /* Used to save the central record of archive when adding */
  111. ULONG Adding;  /* Set to 0x12345678 if adding, cleared afterwards */
  112. ULONG pkzOldCenRecSize;
  113. APTR  pkzOldCenRec;
  114.  
  115. /* End of central record - one per zip archive */
  116. struct PKZEndCRec pkzECRec;
  117.  
  118.  
  119. /*
  120. ** Output a PKZip Header to the specified file.
  121. ** Return TRUE if no errors. else FALSE.
  122. */
  123. BOOL writePKZHead (BPTR outFile, STRPTR origName)
  124. {
  125.   UBYTE Empty[30];
  126.   UWORD nlen;
  127.   
  128.   /* Simply write an empty sizeof (struct PKZHead) + strlen (origName). */
  129.   /* We'll fill in the details later.                                   */
  130.   
  131.   /* Note the date and time */
  132.   startDate = dosDate();
  133.   
  134.   /* Make sure we have a name */
  135.   if (!origName) origName = "disk.adf";
  136.   
  137.   /* Save the current position and name */
  138.   pkzHeadOffset = Seek (outFile, 0, OFFSET_CURRENT);
  139.   pkzOrigName = strdup (origName);
  140.   
  141.   /* Write the `header' and the name to the file */
  142.   if ( Write (outFile, Empty, 30) != 30) return FALSE;
  143.   
  144.   /* Get the filename length and write the filename */
  145.   nlen = strlen (origName);  
  146.   if ( Write (outFile, origName, nlen) != nlen) return FALSE;
  147.   
  148.   /* PKZip file is now ready to recieve the compressed data */
  149.   return TRUE;
  150. }
  151.  
  152.  
  153. /*
  154. ** Finish writing a PKZip file.
  155. ** Return TRUE if no errors. else FALSE.
  156. */
  157. BOOL finishPKZFile (BPTR outFile, ULONG CRC, ULONG CSize, ULONG USize)
  158. {
  159.   LONG  CRecPos;
  160.   ULONG Date;
  161.   UWORD FNameLen, ZCommentLen;
  162.   /* Local header - one per zipped file */
  163.   struct PKZHead pkzHead     = {LHD_MAGNUM,  /* PKZip Magic Number         */
  164.                                 20,          /* Min. UnZip version (2.0)   */
  165.                                 0,           /* Min. OS (MS-DOS)           */
  166.                                 0,           /* Flags                      */
  167.                                 0x0800,      /* Compression Method (LE)    */
  168.                                 0,           /* Date (to be filled in)     */
  169.                                 0,           /* CRC (to be filled in)      */
  170.                                 0,           /* CSize (to be filled in)    */
  171.                                 0,           /* USize (to be filled in)    */
  172.                                 0,           /* Filename length (tbfi)     */
  173.                                 0};          /* Extra field length         */
  174.   /* Central record entry - one per zipped file */
  175.   struct PKZCenRec pkzCenRec = {CRC_MAGNUM,  /* Central Rec Magic Number   */
  176.                                 20,          /* Zip version (2.0)          */
  177.                                 1,           /* Host OS (Amiga)            */
  178.                                 20,          /* Min. UnZip version (2.0)   */
  179.                                 0,           /* Min. Host OS (MS-DOS)      */
  180.                                 0,           /* Flags                      */
  181.                                 0x0800,      /* Compression Mode (LE)      */
  182.                                 0,           /* Date (to be filled in)     */
  183.                                 0,           /* CRC (to be filled in)      */
  184.                                 0,           /* CSize (to be filled in)    */
  185.                                 0,           /* USize (to be filled in)    */
  186.                                 0,           /* File Name Length (tbfi)    */
  187.                                 0,           /* Extra field length         */
  188.                                 0,           /* File comment length        */
  189.                                 0,           /* Disk Number                */
  190.                                 0,           /* File Type (binary)         */
  191.                                 0,           /* MS-DOS file attributes     */
  192.                                 0x0D04,      /* File Attributes (rw-d)     */
  193.                                 0};          /* Local header offset (tbfi) */
  194.   
  195.   
  196.   /* Seek to the local header and save the `current' position */
  197.   CRecPos = Seek (outFile, pkzHeadOffset, OFFSET_BEGINNING);
  198.   
  199.   /* 'pre-process' CRC, CSize and USize */
  200.   CRC   = LEL (CRC);
  201.   CSize = LEL (CSize);
  202.   USize = LEL (USize);
  203.   Date  = LEL (startDate);
  204.   FNameLen = strlen (pkzOrigName);
  205.   ZCommentLen = strlen (ZipComment);
  206.   
  207.   /* Fill the structures */
  208.   pkzHead.Date       = Date;
  209.   pkzHead.CRC        = CRC;
  210.   pkzHead.CSize      = CSize;
  211.   pkzHead.USize      = USize;
  212.   pkzHead.FNameLen   = LES (FNameLen);
  213.   
  214.   pkzCenRec.Date     = Date;
  215.   pkzCenRec.CRC      = CRC;
  216.   pkzCenRec.CSize    = CSize;
  217.   pkzCenRec.USize    = USize;
  218.   pkzCenRec.FNameLen = LES (FNameLen);
  219.   pkzCenRec.LHeadOff = LEL (pkzHeadOffset);
  220.   
  221.   /* Fill the End of Central Record structure */
  222.   pkzECRec.MagicNum      = ECR_MAGNUM;
  223.   pkzECRec.DiskNum       = 0;
  224.   pkzECRec.CentRecDisk   = 0;
  225.   pkzECRec.CenRecOffset  = LEL (CRecPos);
  226.   pkzECRec.CommentLen    = LES (ZCommentLen);
  227.   if (Adding == 0x12345678)
  228.   {
  229.     pkzECRec.EntriesOnDisk = LES ( LES (pkzECRec.EntriesOnDisk) + 1);
  230.     pkzECRec.CenRecEntries = LES ( LES (pkzECRec.CenRecEntries) + 1);
  231.     pkzECRec.CenRecSize    = LEL (pkzOldCenRecSize + 46 + FNameLen);  
  232.   }
  233.   else
  234.   {
  235.     pkzECRec.EntriesOnDisk = LES (1);
  236.     pkzECRec.CenRecEntries = LES (1);
  237.     pkzECRec.CenRecSize    = LEL (46 + FNameLen);
  238.   }
  239.   
  240.   /* Write the Local Header and seek to the Central Record offset */
  241.   if ( Write (outFile, &pkzHead, 30) != 30) return FALSE;
  242.   /* Write (outFile, pkzOrigName, FNameLen); */ /* Already done */
  243.   Seek (outFile, CRecPos, OFFSET_BEGINNING);
  244.   
  245.   if (Adding == 0x12345678)
  246.   {
  247.     /* Write the old central record before adding the new one */
  248.     if ( Write (outFile, pkzOldCenRec, pkzOldCenRecSize) != pkzOldCenRecSize)
  249.       return FALSE;
  250.   }
  251.   
  252.   /* Write the Central Record and origName */
  253.   if ( Write (outFile, &pkzCenRec, 46) != 46) return FALSE;
  254.   if ( Write (outFile, pkzOrigName, FNameLen) != FNameLen) return FALSE;
  255.   
  256.   /* Write the End of Central Record and File Comment */
  257.   if ( Write (outFile, &pkzECRec, 22) != 22) return FALSE;
  258.   if ( Write (outFile, ZipComment, ZCommentLen) != ZCommentLen) return FALSE;
  259.   
  260.   /* Don't need the name anymore */
  261.   free (pkzOrigName);
  262.   
  263.   /* Don't need the old record anymore */
  264.   if (Adding == 0x123456780) free (pkzOldCenRec);
  265.   
  266.   /* No longer adding */
  267.   Adding = 0;
  268.   
  269.   /* Done! */
  270.   return TRUE;
  271. }
  272.  
  273.  
  274. /*
  275. ** Output a PKZip Header to the specified file, adding
  276. ** it to an existing archive.
  277. ** Return TRUE if no errors. else FALSE.
  278. */
  279. BOOL writePKZHeadAdd (BPTR outFile, STRPTR origName)
  280. {
  281.   LONG DOSError;
  282.  
  283.   /* We're adding */
  284.   Adding = 0x12345678;
  285.   FPuts (StdOut, "Updating archive.\n");
  286.  
  287.   /* Get the End of Central Record */
  288.   if ( GotoFirstField (outFile, ECR_MAGNUM) == -1)
  289.   {
  290.     DOSError = IoErr();
  291.     
  292.     if (DOSError)
  293.     {
  294.       FPrintf (StdErr, "%s: Error reading file - ", ProgName);
  295.       reportDOSError(DOSError);
  296.     }
  297.     else
  298.       FPrintf (StdErr, "%s: Can't add ADF, Not a Zip file.\n", ProgName);
  299.     
  300.     cleanExit (RETURN_ERROR, NULL);
  301.   }
  302.   if ( Read (outFile, &pkzECRec, 22) != 22) return FALSE;
  303.  
  304.   /* We'll be adding this header at the current position of the Cent Rec */
  305.   pkzHeadOffset = LEL (pkzECRec.CenRecOffset);
  306.   
  307.   /* Save the current central record for re-writing later */
  308.   pkzOldCenRecSize = LEL (pkzECRec.CenRecSize);
  309.   pkzOldCenRec = calloc (pkzOldCenRecSize, 1);
  310.   if (!pkzOldCenRec)
  311.   {
  312.     /* Memory error */
  313.     return FALSE;
  314.   }
  315.   Seek (outFile, pkzHeadOffset, OFFSET_BEGINNING);
  316.   if ( Read (outFile, pkzOldCenRec, pkzOldCenRecSize) != pkzOldCenRecSize)
  317.     return FALSE;
  318.   
  319.   /* Write our dummy header and get outa here */
  320.   Seek (outFile, pkzHeadOffset, OFFSET_BEGINNING);  
  321.   return writePKZHead (outFile, origName);
  322. }
  323.  
  324.  
  325. /*
  326. ** Finish writing a PKZip file, adding new a new record.
  327. ** Return TRUE if no errors. else FALSE.
  328. */
  329. BOOL finishPKZFileAdd (BPTR outFile, ULONG CRC, ULONG CSize, ULONG USize)
  330. {
  331.   return finishPKZFile (outFile, CRC, CSize, USize);
  332. }
  333.  
  334.  
  335. /*
  336. ** Skip the PKZip Header of a specified file.
  337. ** If origName is specified, search for that name within file.
  338. ** Return TRUE if no errors. else FALSE.
  339. */
  340. BOOL skipPKZHead (BPTR inFile, STRPTR origName)
  341. {
  342.   UBYTE FName[128];  /* This should be large enough */
  343.   struct PKZHead pkzHead;
  344.   struct PKZCenRec pkzCenRec;
  345.   ULONG origNameLen, pOrigNameLen;
  346.   LONG DOSError;
  347.   STRPTR pOrigName;
  348.   UWORD nlen, elen, clen;
  349.   int i;
  350.   
  351.   
  352.   if (origName)
  353.   {
  354.     /* Get the first central record entry */
  355.     if ( GotoFirstField (inFile, CRC_MAGNUM) == -1)
  356.     {
  357.       DOSError = IoErr();
  358.       
  359.       if (DOSError)
  360.       {
  361.         FPrintf (StdErr, "%s: Error reading file - ", ProgName);
  362.         reportDOSError(DOSError);
  363.       }
  364.       else
  365.         FPrintf (StdErr, "%s: Error - Not a Zip file.\n", ProgName);
  366.       
  367.       cleanExit (RETURN_ERROR, NULL);
  368.     }
  369.     
  370.     /* Parse the pattern */
  371.     origNameLen = strlen (origName);
  372.     for (i=0; i < origNameLen; i++)
  373.       origName[i] = toupper (origName[i]);
  374.     
  375.     pOrigNameLen = (2 * origNameLen) + 2;
  376.     pOrigName = calloc (pOrigNameLen, 1);
  377.     if (!pOrigName)
  378.     {
  379.       FPrintf (StdErr, "%s: Error - Not enough memory to match filename.\n",
  380.                        ProgName);
  381.       cleanExit (RETURN_FAIL, ERROR_NO_FREE_STORE);
  382.     }
  383.     
  384.     if (ParsePatternNoCase (origName, pOrigName, pOrigNameLen) == -1)
  385.     {
  386.       FPrintf (StdErr, "%s: Error - Can't match filename.\n", ProgName);
  387.       cleanExit (RETURN_FAIL, NULL);
  388.     }
  389.     
  390.     /* Now go through all the names and search for a match */
  391.     while (1)
  392.     {
  393.       /* Read the entry */
  394.       if ( Read (inFile, &pkzCenRec, 46) != 46) return FALSE;
  395.       
  396.       /* Test the current magic number */
  397.       if (pkzCenRec.MagicNum != CRC_MAGNUM)
  398.       {
  399.         /* We went through the whole list without a match */
  400.         FPrintf (StdErr, "%s: No match for filename.\n", ProgName);
  401.         cleanExit (RETURN_WARN, NULL);
  402.       }
  403.       
  404.       /* Get extra info lengths */
  405.       nlen = LES (pkzCenRec.FNameLen);
  406.       elen = LES (pkzCenRec.EFieldLen);
  407.       clen = LES (pkzCenRec.FCommentLen);
  408.       
  409.       /* Read in the name */
  410.       if ( Read (inFile, FName, nlen) != nlen) return FALSE;
  411.       FName[nlen] = 0;
  412.       
  413.       /* Attempt to match the pattern */
  414.       if (MatchPatternNoCase (pOrigName, FName))
  415.       {
  416.         /* Output information */
  417.         FPrintf (StdOut, "Extracting %s.\n", FName);
  418.       
  419.         /* Got a name, seek to the start of this item */
  420.         Seek (inFile, LEL (pkzCenRec.LHeadOff), OFFSET_BEGINNING);
  421.         
  422.         /* Break the loop */
  423.         break;
  424.       }
  425.       
  426.       /* No match, move along */
  427.       Seek (inFile, elen + clen, OFFSET_CURRENT);
  428.     }
  429.     
  430.     free (pOrigName);
  431.   }
  432.   
  433.   /* Save the current position */
  434.   pkzHeadOffset = Seek (inFile, 0, OFFSET_CURRENT);
  435.   
  436.   /* Consume the header and extra info */
  437.   if ( Read (inFile, &pkzHead, 30) != 30) return FALSE;
  438.   if (pkzHead.MagicNum != LHD_MAGNUM)
  439.   {
  440.     FPrintf (StdErr, 
  441.              "%s: Error - Didn't read a Local header - not a Zip file.\n",
  442.              ProgName);
  443.     cleanExit (RETURN_ERROR, NULL);    
  444.   }
  445.   nlen = LES (pkzHead.FNameLen);
  446.   elen = LES (pkzHead.EFieldLen);
  447.   Seek (inFile, (nlen + elen), OFFSET_CURRENT);
  448.   
  449.   /* Check the compression */
  450.   if ( LES (pkzHead.CMethod) != 8)
  451.   {
  452.     FPrintf (StdErr,
  453.              "%s: Error - Only Deflate compression method supported.\n",
  454.              ProgName);
  455.     cleanExit (RETURN_ERROR, NULL);
  456.   }
  457.   
  458.   /* inFile now points to the compressed data */
  459.   return TRUE;
  460. }
  461.  
  462.  
  463. /*
  464. ** Read the header of a PKZip file and return the CRC
  465. ** and USize in supplied arrays.
  466. ** Return TRUE if no errors, else FALSE.
  467. */
  468. BOOL readPKZTail (BPTR inFile, ULONG *CRC, ULONG *USize)
  469. {
  470.   struct PKZHead pkzHead;
  471.   LONG oldp;
  472.   
  473.   /* Get the `current' header */
  474.   oldp = Seek (inFile, pkzHeadOffset, OFFSET_BEGINNING);
  475.   if ( Read (inFile, &pkzHead, 30) != 30) return FALSE;
  476.   Seek (inFile, oldp, OFFSET_BEGINNING);
  477.   
  478.   /* Fill the return values */
  479.   *CRC   = LEL (pkzHead.CRC);
  480.   *USize = LEL (pkzHead.USize);
  481.   
  482.   return TRUE;
  483. }
  484.  
  485.  
  486. /*
  487. ** Jump to the start of the first field with FieldID.
  488. ** Return the offset as well as setting the file pointer.
  489. ** Return -1 for error.
  490. */
  491. LONG GotoFirstField (BPTR file, ULONG FieldID)
  492. {
  493.   UBYTE Buffer[46];
  494.   /* Convenient `gateways' */
  495.   struct PKZHead *pkzHead = (struct PKZHead *) Buffer;
  496.   struct PKZCenRec *pkzCenRec = (struct PKZCenRec *) Buffer;
  497.   ULONG item, nextSeek;
  498.   int cont = TRUE;
  499.   
  500.   
  501.   do 
  502.   {
  503.     /* Read in an item */
  504.     if ( Read (file, &item, 4) != 4) return -1;
  505.     
  506.     /* Identify item and get the rest of the field if required */
  507.     switch (item) {
  508.     case LHD_MAGNUM:
  509.       /* Local header */
  510.       
  511.       /* Check if want to stop here */
  512.       if (FieldID == LHD_MAGNUM)
  513.         cont = FALSE;
  514.       else
  515.       {
  516.         /* Move along */
  517.         if ( Read (file, Buffer+4, 26) != 26) return -1;  /* 26 = 30 - 4 */
  518.         nextSeek = LES (pkzHead->FNameLen) + LES (pkzHead->EFieldLen) + 
  519.                    LEL (pkzHead->CSize);
  520.         Seek (file, nextSeek, OFFSET_CURRENT);
  521.       }
  522.       break;
  523.     
  524.     case CRC_MAGNUM:
  525.       /* Central record entry */
  526.       
  527.       /* Check if want to stop here */
  528.       if (FieldID == CRC_MAGNUM)
  529.         cont = FALSE;
  530.       else
  531.       {
  532.         /* Move along */
  533.         if ( Read (file, Buffer+4, 42) != 42) return -1;  /* 42 = 46 - 4 */
  534.         nextSeek = LES (pkzCenRec->FNameLen) + LES (pkzCenRec->EFieldLen) + 
  535.                    LES (pkzCenRec->FCommentLen);
  536.         Seek (file, nextSeek, OFFSET_CURRENT);
  537.       }
  538.       break;
  539.     
  540.     case ECR_MAGNUM:
  541.       /* End of central record */
  542.       
  543.       /* Final destination - stop here! */
  544.       cont = FALSE;
  545.       break;
  546.     
  547.     default:
  548.       /* Can't id the field - error */
  549.       return -1;
  550.     }    
  551.   } while (cont);
  552.   
  553.   /* Seek back to the start of the current item */
  554.   Seek (file, -4, OFFSET_CURRENT);
  555.   
  556.   /* return the current offset */
  557.   return Seek (file, 0, OFFSET_CURRENT);
  558. }
  559.